package com.itangqi.waveloadingview;

import com.itangqi.waveloadingview.utils.AttrValue;
import com.itangqi.waveloadingview.utils.TextUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.PixelMapShader;
import ohos.agp.render.Shader;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.app.dispatcher.task.Revocable;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;

import java.math.BigDecimal;

/**
 * 实时波纹加载特效的控件
 *
 * @since 2021-04-08
 */
public class WaveLoadingView extends Component {
    /**
     * +------------------------+RECTANGLE
     * | wave length - 波长      |__________
     * |   /\          |   /\   |  |
     * |  /  \         |  /  \  | amplitude - 振幅
     * | /    \        | /    \ |  |
     * |/      \       |/      \|__|_______
     * |        \      /        |  |
     * |         \    /         |  |
     * |          \  /          |  |
     * |           \/           | water level - 水位
     * |                        |  |
     * |                        |  |
     * +------------------------+__|_______
     */
    private static final float DEFAULT_AMPLITUDE_RATIO = 0.1f;
    private static final float DEFAULT_AMPLITUDE_VALUE = 70.0f;
    private static final float DEFAULT_EDGE_WIDTH = 0.5f;
    private static final float DEFAULT_WAVE_LENGTH_RATIO = 1.0f;
    private static final float DEFAULT_WIDTH_RATE = 2f;
    private static final float COLOR_FACTOR = 0.3f;
    private static final float HEIGHT_RATE = 10.0f;
    private static final float FLOAT_TWO = 2.0f;
    private static final float FLOAT_THREE = 3.0f;
    private static final int DEFAULT_WAVE_PROGRESS_VALUE = 50;
    private static final int DEFAULT_WAVE_PROGRESS_TATE = 100;
    private static final int NUMBER_TWO = 2;
    private static final int NUMBER_EIGHT = 8;
    private static final int PHASE_RATE = 15;
    private static final int ONE_BYTE = 8;
    private static final int TWO_BYTES = 16;
    private static final int EIGHT_BITS = 0xFF;
    private static final Color DEFAULT_WAVE_COLOR =
            new Color(Color.getIntColor("#212121"));
    private static final Color DEFAULT_WAVE_BACKGROUND_COLOR =
            new Color(Color.getIntColor("#00000000"));
    private static final Color DEFAULT_TITLE_COLOR =
            new Color(Color.getIntColor("#212121"));
    private static final Color DEFAULT_STROKE_COLOR = Color.TRANSPARENT;
    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_TITLE_STROKE_WIDTH = 0;

    private static final int DEFAULT_WAVE_SHAPE = ShapeType.CIRCLE.ordinal();
    private static final int DEFAULT_TRIANGLE_DIRECTION =
            TriangleDirection.NORTH.ordinal();
    private static final int DEFAULT_ROUND_RECTANGLE_X_AND_Y = 30;
    private static final int DEFAULT_TITLE_TOP_SIZE = 54;
    private static final int DEFAULT_TITLE_CENTER_SIZE = 66;
    private static final int DEFAULT_TITLE_BOTTOM_SIZE = 54;
    private static final int FIRST_DELAY_TIME = 500;
    private static final int INTERVAL_TIME = 100;

    /**
     * 图形枚举类
     *
     * @since 2021-04-08
     */
    public enum ShapeType {

        /**
         * 三角形
         */
        TRIANGLE,

        /**
         * 圆
         */
        CIRCLE,

        /**
         * 矩形
         */
        SQUARE,

        /**
         * 圆角矩形
         */
        RECTANGLE
    }

    /**
     * 三角形方向枚举类
     *
     * @since 2021-04-08
     */
    public enum TriangleDirection {
        /**
         * 北
         */
        NORTH,

        /**
         * 难
         */
        SOUTH,

        /**
         * 东
         */
        EAST,

        /**
         * 西
         */
        WEST
    }

    private Color mWaveBgColor;
    private Color mWaveColor;
    private int mShapeType;
    private int mTriangleDirection;
    private int mRoundRectangleXy;

    // Properties.
    private String mTopTitle;
    private String mCenterTitle;
    private String mBottomTitle;

    // 水位 0 ~ 100
    private int mProgressValue = DEFAULT_WAVE_PROGRESS_VALUE;
    private boolean mIsRoundRectangle;

    private PixelMapShader pixelMapShader;

    // Paint to draw wave.
    private Paint mWavePaint;

    // Paint to draw waveBackground.
    private Paint mWaveBgPaint;

    // Paint to draw border.
    private Paint mBorderPaint;

    // Point to draw title.
    private Paint mTopTitlePaint;
    private Paint mBottomTitlePaint;
    private Paint mCenterTitlePaint;

    // 文字背景
    private Paint mTopTitleStrokePaint;
    private Paint mBottomTitleStrokePaint;
    private Paint mCenterTitleStrokePaint;

    private final float customFrequency = 1.0f;
    private float phase = 0f; // 相位
    private float amplitude = DEFAULT_AMPLITUDE_VALUE; // 振幅 1~100

    private boolean isResume = true;
    private boolean isStart = true;
    private boolean isSetSizeBeforeDraw = false;
    private Revocable revocable; // 取消异步任务的接口

    private Size estimateSize;

    /**
     * 构造器
     *
     * @param context 上下文
     */
    public WaveLoadingView(final Context context) {
        this(context, null);
    }

    /**
     * 构造器
     *
     * @param context 上下文
     * @param attrSet 动态属性
     */
    public WaveLoadingView(final Context context, final AttrSet attrSet) {
        this(context, attrSet, "");
    }

    /**
     * 构造器
     *
     * @param context 上下文
     * @param attrSet 动态属性
     * @param styleName 样式名
     */
    public WaveLoadingView(final Context context, final AttrSet attrSet,
                           final String styleName) {
        super(context, attrSet, styleName);
        init(context, attrSet);
    }

    /**
     * 初始化
     *
     * @param context 上下文
     * @param attrSet 动态属性
     */
    private void init(final Context context, final AttrSet attrSet) {
        mShapeType = AttrValue.get(attrSet, "wlv_shapeType",
                DEFAULT_WAVE_SHAPE);

        // Init Wave
        mWaveColor = AttrValue.get(attrSet, "wlv_waveColor",
                DEFAULT_WAVE_COLOR);
        mWaveBgColor = AttrValue.get(attrSet, "wlv_wave_background_Color",
                DEFAULT_WAVE_BACKGROUND_COLOR);

        // Init Progress
        mProgressValue = AttrValue.get(attrSet, "wlv_progressValue",
                DEFAULT_WAVE_PROGRESS_VALUE);

        // Init RoundRectangle
        mIsRoundRectangle = AttrValue.get(attrSet, "wlv_round_rectangle",
                false);
        mRoundRectangleXy = AttrValue.get(attrSet,
                "wlv_round_rectangle_x_and_y", DEFAULT_ROUND_RECTANGLE_X_AND_Y);

        // Init Triangle direction
        mTriangleDirection = AttrValue.get(attrSet, "wlv_triangle_direction",
                DEFAULT_TRIANGLE_DIRECTION);
        mTopTitle = AttrValue.get(attrSet, "wlv_titleTop", "");
        mCenterTitle = AttrValue.get(attrSet, "wlv_titleCenter", "");
        mBottomTitle = AttrValue.get(attrSet, "wlv_titleBottom", "");

        initPaint(attrSet);
        setEstimateSizeListener((widthMeasureSpec, heightMeasureSpec) -> {
            estimateSize = doEstimateSize(widthMeasureSpec, heightMeasureSpec);
            return false;
        });

        addDrawTask(getDrawTask());

        getContext().getUITaskDispatcher().delayDispatch(new Runnable() {
            int num = 0;

            @Override
            public void run() {
                if (isStart) {
                    if (isResume) {
                        setPhase(num++);
                    }
                } else {
                    num = 0;
                }
                revocable = getContext().getUITaskDispatcher().
                        delayDispatch(this, INTERVAL_TIME);
            }
        }, FIRST_DELAY_TIME);
    }

    /**
     * 初始化画笔
     *
     * @param attrSet
     */
    private void initPaint(final AttrSet attrSet) {
        initWave(attrSet);
        initBorder(attrSet);
        initTopTitle(attrSet);
        initCenterTitle(attrSet);
        initBottomTitle(attrSet);
    }

    private void initWave(final AttrSet attrSet) {
        // The ANTI_ALIAS_FLAG bit AntiAliasing smooths out the edges of what
        // is being drawn,
        // but is has no impact on the interior of the shape.
        mWavePaint = new Paint();
        mWavePaint.setAntiAlias(true);

        mWaveBgPaint = new Paint();
        mWaveBgPaint.setAntiAlias(true);
        mWaveBgPaint.setColor(mWaveBgColor);
    }

    private void initBorder(final AttrSet attrSet) {
        // Init Border
        mBorderPaint = new Paint();
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setStyle(Paint.Style.STROKE_STYLE);
        mBorderPaint.setStrokeWidth(AttrValue.getDimension(attrSet,
                "wlv_borderWidth", DEFAULT_BORDER_WIDTH));
        mBorderPaint.setColor(AttrValue.get(attrSet, "wlv_borderColor",
                DEFAULT_WAVE_COLOR));
    }

    private void initTopTitle(final AttrSet attrSet) {
        // Init Top Title
        mTopTitlePaint = new Paint();
        mTopTitlePaint.setColor(AttrValue.get(attrSet, "wlv_titleTopColor",
                DEFAULT_TITLE_COLOR));
        mTopTitlePaint.setStyle(Paint.Style.FILL_STYLE);
        mTopTitlePaint.setAntiAlias(true);
        mTopTitlePaint.setTextSize(AttrValue.getDimension(attrSet,
                "wlv_titleTopSize", DEFAULT_TITLE_TOP_SIZE));

        mTopTitleStrokePaint = new Paint();
        mTopTitleStrokePaint.setColor(AttrValue.get(attrSet,
                "wlv_titleTopStrokeColor", DEFAULT_STROKE_COLOR));
        mTopTitleStrokePaint.setStrokeWidth(AttrValue.getDimension(attrSet,
                "wlv_titleTopStrokeWidth", DEFAULT_TITLE_STROKE_WIDTH));
        mTopTitleStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
        mTopTitleStrokePaint.setAntiAlias(true);
        mTopTitleStrokePaint.setTextSize(mTopTitlePaint.getTextSize());
    }

    private void initCenterTitle(final AttrSet attrSet) {
        // Init Center Title
        mCenterTitlePaint = new Paint();
        mCenterTitlePaint.setColor(AttrValue.get(attrSet,
                "wlv_titleCenterColor", DEFAULT_TITLE_COLOR));
        mCenterTitlePaint.setStyle(Paint.Style.FILL_STYLE);
        mCenterTitlePaint.setAntiAlias(true);
        mCenterTitlePaint.setTextSize(AttrValue.getDimension(attrSet,
                "wlv_titleCenterSize", DEFAULT_TITLE_CENTER_SIZE));

        mCenterTitleStrokePaint = new Paint();
        mCenterTitleStrokePaint.setColor(AttrValue.get(attrSet,
                "wlv_titleCenterStrokeColor", DEFAULT_STROKE_COLOR));
        mCenterTitleStrokePaint.setStrokeWidth(AttrValue.getDimension(attrSet,
                "wlv_titleCenterStrokeWidth", DEFAULT_TITLE_STROKE_WIDTH));
        mCenterTitleStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
        mCenterTitleStrokePaint.setAntiAlias(true);
        mCenterTitleStrokePaint.setTextSize(mCenterTitlePaint.getTextSize());
    }

    private void initBottomTitle(final AttrSet attrSet) {
        // Init Bottom Title
        mBottomTitlePaint = new Paint();
        mBottomTitlePaint.setColor(AttrValue.get(attrSet,
                "wlv_titleBottomColor", DEFAULT_TITLE_COLOR));
        mBottomTitlePaint.setStyle(Paint.Style.FILL_STYLE);
        mBottomTitlePaint.setAntiAlias(true);
        mBottomTitlePaint.setTextSize(AttrValue.getDimension(attrSet,
                "wlv_titleBottomSize", DEFAULT_TITLE_BOTTOM_SIZE));

        mBottomTitleStrokePaint = new Paint();
        mBottomTitleStrokePaint.setColor(AttrValue.get(attrSet,
                "wlv_titleBottomStrokeColor", DEFAULT_STROKE_COLOR));
        mBottomTitleStrokePaint.setStrokeWidth(AttrValue.getDimension(attrSet,
                "wlv_titleBottomStrokeWidth", DEFAULT_TITLE_STROKE_WIDTH));
        mBottomTitleStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
        mBottomTitleStrokePaint.setAntiAlias(true);
        mBottomTitleStrokePaint.setTextSize(mBottomTitlePaint.getTextSize());
    }

    private DrawTask getDrawTask() {
        DrawTask drawPic = (component, canvas) -> {
            if (!isSetSizeBeforeDraw) {
                setComponentSize(estimateSize.width, estimateSize.height);
                isSetSizeBeforeDraw = true;
            }

            updateWaveShader();

            // Draw Wave.
            // Modify paint shader according to mShowWave state.
            if (pixelMapShader != null) {
                // First call after mShowWave, assign it to our paint.
                if (mWavePaint.getShader() == null) {
                    mWavePaint.setShader(pixelMapShader,
                            Paint.ShaderType.PIXELMAP_SHADER);
                }

                drawExternalGraph(canvas);

                drawText(canvas);
            } else {
                mWavePaint.setShader(null, Paint.ShaderType.PIXELMAP_SHADER);
            }
        };
        return drawPic;
    }

    private void drawExternalGraph(final Canvas canvas) {
        // Get borderWidth.
        float borderWidth = mBorderPaint.getStrokeWidth();
        ShapeType shapeType = getShapeType(mShapeType);
        switch (shapeType) {
            // Draw triangle
            case TRIANGLE:
                // Currently does not support the border settings
                drawTriangle(canvas);
                break;

            // Draw circle
            case CIRCLE:
                drawCircle(borderWidth, canvas);
                break;

            // Draw square
            case SQUARE:
                drawSquare(borderWidth, canvas);
                break;

            // Draw rectangle
            case RECTANGLE:
                drawRectangle(borderWidth, canvas);
                break;
            default:
                break;
        }
    }

    private void drawTriangle(final Canvas canvas) {
        Point start = new Point(0, getHeight());
        Path triangle = getEquilateralTriangle(start, getWidth(), getHeight(),
                mTriangleDirection);
        canvas.drawPath(triangle, mWaveBgPaint);
        canvas.drawPath(triangle, mWavePaint);
    }

    private void drawCircle(float borderWidth, final Canvas canvas) {
        if (borderWidth > 0) {
            canvas.drawCircle(getWidth() / DEFAULT_WIDTH_RATE, getHeight() / DEFAULT_WIDTH_RATE,
                    (getWidth() - borderWidth) / DEFAULT_WIDTH_RATE - 1f, mBorderPaint);
        }

        float radius = getWidth() / DEFAULT_WIDTH_RATE - borderWidth;

        // Draw background
        canvas.drawCircle(getWidth() / DEFAULT_WIDTH_RATE, getHeight() / DEFAULT_WIDTH_RATE, radius,
                mWaveBgPaint);
        canvas.drawCircle(getWidth() / DEFAULT_WIDTH_RATE, getHeight() / DEFAULT_WIDTH_RATE, radius,
                mWavePaint);
    }

    private void drawSquare(float borderWidth, final Canvas canvas) {
        if (borderWidth > 0) {
            canvas.drawRect(borderWidth / DEFAULT_WIDTH_RATE, borderWidth / DEFAULT_WIDTH_RATE,
                    getWidth() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                    getHeight() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                    mBorderPaint);
        }

        canvas.drawRect(borderWidth, borderWidth, getWidth() - borderWidth,
                getHeight() - borderWidth, mWaveBgPaint);
        canvas.drawRect(borderWidth, borderWidth, getWidth() - borderWidth,
                getHeight() - borderWidth, mWavePaint);
    }

    /**
     * 画矩形
     *
     * @param borderWidth 矩形宽
     * @param canvas 画布
     */
    private void drawRectangle(float borderWidth, final Canvas canvas) {
        if (mIsRoundRectangle) {
            RectFloat rect;
            if (borderWidth > 0) {
                rect = new RectFloat(borderWidth / DEFAULT_WIDTH_RATE, borderWidth / DEFAULT_WIDTH_RATE,
                        getWidth() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                        getHeight() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH);
                canvas.drawRoundRect(rect, mRoundRectangleXy,
                        mRoundRectangleXy, mBorderPaint);
            } else {
                rect = new RectFloat(0, 0, getWidth(), getHeight());
            }
            canvas.drawRoundRect(rect, mRoundRectangleXy, mRoundRectangleXy,
                    mWaveBgPaint);
            canvas.drawRoundRect(rect, mRoundRectangleXy, mRoundRectangleXy,
                    mWavePaint);
        } else {
            if (borderWidth > 0) {
                canvas.drawRect(borderWidth / DEFAULT_WIDTH_RATE, borderWidth / DEFAULT_WIDTH_RATE,
                        getWidth() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                        getHeight() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                        mWaveBgPaint);
                canvas.drawRect(borderWidth / DEFAULT_WIDTH_RATE, borderWidth / DEFAULT_WIDTH_RATE,
                        getWidth() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                        getHeight() - borderWidth / DEFAULT_WIDTH_RATE - DEFAULT_EDGE_WIDTH,
                        mWavePaint);
            } else {
                canvas.drawRect(0, 0, getWidth(), getHeight(), mWaveBgPaint);
                canvas.drawRect(0, 0, getWidth(), getHeight(), mWavePaint);
            }
        }
    }

    private void drawText(final Canvas canvas) {
        if (!TextUtils.isEmpty(mTopTitle)) {
            float top = mTopTitlePaint.measureText(mTopTitle);

            // Draw the stroke of top text
            canvas.drawText(mTopTitleStrokePaint, mTopTitle,
                    (getWidth() - top) / NUMBER_TWO, getHeight() * NUMBER_TWO / HEIGHT_RATE);

            // Draw the top text
            canvas.drawText(mTopTitlePaint, mTopTitle, (getWidth() - top) / NUMBER_TWO,
                    getHeight() * NUMBER_TWO / HEIGHT_RATE);
        }

        if (!TextUtils.isEmpty(mCenterTitle)) {
            float middle = mCenterTitlePaint.measureText(mCenterTitle);

            // Draw the stroke of centered text
            canvas.drawText(mCenterTitleStrokePaint, mCenterTitle,
                    (getWidth() - middle) / NUMBER_TWO,
                    (getHeight() >> 1) - ((mCenterTitleStrokePaint.descent()
                            + mCenterTitleStrokePaint.ascent()) / NUMBER_TWO));

            // Draw the centered text
            canvas.drawText(mCenterTitlePaint, mCenterTitle,
                    (getWidth() - middle) / NUMBER_TWO,
                    (getHeight() >> 1) - ((mCenterTitlePaint.descent() + mCenterTitlePaint.ascent()) / NUMBER_TWO));
        }

        if (!TextUtils.isEmpty(mBottomTitle)) {
            float bottom = mBottomTitlePaint.measureText(mBottomTitle);

            // Draw the stroke of bottom text
            canvas.drawText(mBottomTitleStrokePaint, mBottomTitle,
                    (getWidth() - bottom) / NUMBER_TWO,
                    getHeight() * NUMBER_EIGHT / HEIGHT_RATE
                            - ((mBottomTitleStrokePaint.descent()
                            + mBottomTitleStrokePaint.ascent()) / NUMBER_TWO));

            // Draw the bottom text
            canvas.drawText(mBottomTitlePaint, mBottomTitle,
                    (getWidth() - bottom) / NUMBER_TWO,
                    getHeight() * NUMBER_EIGHT / HEIGHT_RATE
                            - ((mBottomTitlePaint.descent()
                            + mBottomTitlePaint.ascent()) / NUMBER_TWO));
        }
    }

    private Size doEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int width = Math.min(widthSize, heightSize);
        return new Size(width, width);
    }

    private double getWx(float beginX) {
        double defaultAngularFrequency = FLOAT_TWO * Math.PI / getWidth();
        return beginX * defaultAngularFrequency * customFrequency - phase * Math.PI;
    }

    private void setPhase(float value) {
        phase = divide(value, PHASE_RATE);
        invalidate();
    }

    /**
     * 设置振幅
     *
     * @param value 振幅值
     */
    public void setAmplitudeRatio(float value) {
        amplitude = value;
        invalidate();
    }

    public float getAmplitudeRatio() {
        return amplitude;
    }

    private float divide(float a, float b) {
        BigDecimal bigA = new BigDecimal(a);
        BigDecimal bigB = new BigDecimal(b);
        BigDecimal result = bigA.divide(bigB, NUMBER_TWO, BigDecimal.ROUND_HALF_UP);
        return result.floatValue();
    }

    private Color adjustAlpha(Color color, float factor) {
        int carInt = color.getValue();
        int alpha = Math.round(Color.alpha(carInt) * factor);
        int red = (carInt >> TWO_BYTES) & EIGHT_BITS;
        int green = (carInt >> ONE_BYTE) & EIGHT_BITS;
        int blue = carInt & EIGHT_BITS;
        return new Color(Color.argb(alpha, red, green, blue));
    }

    /**
     * 取消任务
     */
    public void onDestroy() {
        boolean revoked = revocable.revoke();
    }

    private void updateWaveShader() {
        int width = getWidth();
        int height = getHeight();

        if (width > 0 && height > 0) {
            double defaultAngularFrequency =
                    FLOAT_TWO * Math.PI / DEFAULT_WAVE_LENGTH_RATIO / width;
            float defaultAmplitude = height * DEFAULT_AMPLITUDE_RATIO;

            PixelMap.InitializationOptions options =
                    new PixelMap.InitializationOptions();
            options.size = new Size(width, height);
            options.pixelFormat = PixelFormat.ARGB_8888;
            PixelMap pixelMap = PixelMap.create(options);
            Canvas canvas = new Canvas(new Texture(pixelMap));

            Paint wavePaint = new Paint();
            wavePaint.setStrokeWidth(NUMBER_TWO);
            wavePaint.setAntiAlias(true);

            final int endX = width + 1;
            final int endY = height + 1;
            float[] waveY = new float[endX];

            wavePaint.setColor(adjustAlpha(mWaveColor, COLOR_FACTOR));
            for (int beginX = 0; beginX < endX; beginX++) {
                double wx = beginX * defaultAngularFrequency;

                float beginY = (float) ((width
                        * (1 - ((double) mProgressValue / DEFAULT_WAVE_PROGRESS_TATE)))
                        + ((amplitude / DEFAULT_WAVE_PROGRESS_TATE)
                        * defaultAmplitude * Math.sin(getWx(beginX))));

                canvas.drawLine(new Point(beginX, beginY), new Point(beginX,
                        endY), wavePaint);
                waveY[beginX] = beginY;
            }

            wavePaint.setColor(mWaveColor);
            final int wave2Shift = (int) ((float) width / 4);
            for (int beginX = 0; beginX < endX; beginX++) {
                canvas.drawLine(new Point(beginX,
                                waveY[(beginX + wave2Shift) % endX]),
                        new Point(beginX, endY), wavePaint);
            }

            PixelMapHolder holder = new PixelMapHolder(pixelMap);
            pixelMapShader = new PixelMapShader(holder,
                    Shader.TileMode.REPEAT_TILEMODE,
                    Shader.TileMode.CLAMP_TILEMODE);
            this.mWavePaint.setShader(pixelMapShader,
                    Paint.ShaderType.PIXELMAP_SHADER);
        }
    }

    /**
     * 设置波浪背景颜色
     *
     * @param color
     */
    public void setWaveBgColor(Color color) {
        this.mWaveBgColor = color;
        mWaveBgPaint.setColor(this.mWaveBgColor);
        updateWaveShader();
        invalidate();
    }

    public Color getWaveBgColor() {
        return mWaveBgColor;
    }

    /**
     * 设置波浪颜色
     *
     * @param color
     */
    public void setWaveColor(Color color) {
        mWaveColor = color;
        updateWaveShader();
        invalidate();
    }

    public Color getWaveColor() {
        return mWaveColor;
    }

    /**
     * 设置边界宽度
     *
     * @param width
     */
    public void setBorderWidth(float width) {
        mBorderPaint.setStrokeWidth(width);
        invalidate();
    }

    public float getBorderWidth() {
        return mBorderPaint.getStrokeWidth();
    }

    /**
     * 设置边界颜色
     *
     * @param color
     */
    public void setBorderColor(Color color) {
        mBorderPaint.setColor(color);
        updateWaveShader();
        invalidate();
    }

    public Color getBorderColor() {
        return mBorderPaint.getColor();
    }

    /**
     * 设置形状
     *
     * @param shapeType
     */
    public void setShapeType(ShapeType shapeType) {
        mShapeType = shapeType.ordinal();
        invalidate();
    }

    private ShapeType getShapeType(int type) {
        ShapeType res = null;
        if (type == ShapeType.TRIANGLE.ordinal()) {
            res = ShapeType.TRIANGLE;
        } else if (type == ShapeType.CIRCLE.ordinal()) {
            res = ShapeType.CIRCLE;
        } else if (type == ShapeType.SQUARE.ordinal()) {
            res = ShapeType.SQUARE;
        } else if (type == ShapeType.RECTANGLE.ordinal()) {
            res = ShapeType.RECTANGLE;
        }
        return res;
    }

    /**
     * Water level increases from 0 to the value of WaveView.
     *
     * @param progress Default to be 50.
     */
    public void setProgressValue(int progress) {
        mProgressValue = progress;
        invalidate();
    }

    public int getProgressValue() {
        return mProgressValue;
    }

    /**
     * 设置初始相位
     *
     * @param waveShiftRatio 初始相位
     */
    public void setWaveShiftRatio(float waveShiftRatio) {
        if (this.phase != waveShiftRatio) {
            this.phase = waveShiftRatio;
            invalidate();
        }
    }

    public float getWaveShiftRatio() {
        return phase;
    }

    /**
     * 设置初始水位
     *
     * @param waterLevelRatio 初始水位
     */
    public void setWaterLevelRatio(int waterLevelRatio) {
        if (this.mProgressValue != waterLevelRatio) {
            this.mProgressValue = waterLevelRatio;
            invalidate();
        }
    }

    /**
     * 获取初始水位
     *
     * @return 水位
     */
    public float getWaterLevelRatio() {
        return mProgressValue;
    }

    /**
     * Set the title within the WaveView.
     *
     * @param topTitle Default to be null.
     */
    public void setTopTitle(String topTitle) {
        mTopTitle = topTitle;
    }

    public String getTopTitle() {
        return mTopTitle;
    }

    public void setCenterTitle(String centerTitle) {
        mCenterTitle = centerTitle;
    }

    public String getCenterTitle() {
        return mCenterTitle;
    }

    public void setBottomTitle(String bottomTitle) {
        mBottomTitle = bottomTitle;
    }

    public String getBottomTitle() {
        return mBottomTitle;
    }

    /**
     * 设置顶部标题颜色
     *
     * @param topTitleColor
     */
    public void setTopTitleColor(Color topTitleColor) {
        mTopTitlePaint.setColor(topTitleColor);
    }

    public Color getTopTitleColor() {
        return mTopTitlePaint.getColor();
    }

    /**
     * 设置中间标题
     *
     * @param centerTitleColor
     */
    public void setCenterTitleColor(Color centerTitleColor) {
        mCenterTitlePaint.setColor(centerTitleColor);
    }

    public Color getCenterTitleColor() {
        return mCenterTitlePaint.getColor();
    }

    /**
     * 设置底部标题颜色
     *
     * @param bottomTitleColor
     */
    public void setBottomTitleColor(Color bottomTitleColor) {
        mBottomTitlePaint.setColor(bottomTitleColor);
    }

    public Color getBottomTitleColor() {
        return mBottomTitlePaint.getColor();
    }

    /**
     * 设置顶部标题文字大小
     *
     * @param topTitleSize
     */
    public void setTopTitleSize(int topTitleSize) {
        mTopTitlePaint.setTextSize(topTitleSize);
    }

    public float getTopTitleSize() {
        return mTopTitlePaint.getTextSize();
    }

    /**
     * 设置中间标题文字大小
     *
     * @param centerTitleSize
     */
    public void setCenterTitleSize(int centerTitleSize) {
        mCenterTitlePaint.setTextSize(centerTitleSize);
    }

    public float getCenterTitleSize() {
        return mCenterTitlePaint.getTextSize();
    }

    /**
     * 设置底部标题文字大小
     *
     * @param bottomTitleSize
     */
    public void setBottomTitleSize(int bottomTitleSize) {
        mBottomTitlePaint.setTextSize(bottomTitleSize);
    }

    public float getBottomTitleSize() {
        return mBottomTitlePaint.getTextSize();
    }

    /**
     * 设置顶部标题背景宽度
     *
     * @param topTitleStrokeWidth
     */
    public void setTopTitleStrokeWidth(float topTitleStrokeWidth) {
        mTopTitleStrokePaint.setStrokeWidth(topTitleStrokeWidth);
    }

    /**
     * 设置顶部标题背景颜色
     *
     * @param topTitleStrokeColor
     */
    public void setTopTitleStrokeColor(Color topTitleStrokeColor) {
        mTopTitleStrokePaint.setColor(topTitleStrokeColor);
    }

    /**
     * 设置底部标题背景宽度
     *
     * @param bottomTitleStrokeWidth
     */
    public void setBottomTitleStrokeWidth(float bottomTitleStrokeWidth) {
        mBottomTitleStrokePaint.setStrokeWidth(bottomTitleStrokeWidth);
    }

    /**
     * 开始动画
     */
    public void startAnimation() {
        isStart = true;
    }

    /**
     * 停止动画
     */
    public void endAnimation() {
        isStart = false;
    }

    /**
     * 取消动画
     */
    public void cancelAnimation() {
        isStart = false;
    }

    /**
     * 暂停动画
     */
    public void pauseAnimation() {
        isResume = false;
    }

    /**
     * 重新开始动画
     */
    public void resumeAnimation() {
        isResume = true;
    }

    /**
     * 设置底部标题背景颜色
     *
     * @param bottomTitleStrokeColor
     */
    public void setBottomTitleStrokeColor(Color bottomTitleStrokeColor) {
        mBottomTitleStrokePaint.setColor(bottomTitleStrokeColor);
    }

    /**
     * 设置中间标题背景宽度
     *
     * @param centerTitleStrokeWidth
     */
    public void setCenterTitleStrokeWidth(float centerTitleStrokeWidth) {
        mCenterTitleStrokePaint.setStrokeWidth(centerTitleStrokeWidth);
    }

    /**
     * 设置中间标题背景颜色
     *
     * @param centerTitleStrokeColor
     */
    public void setCenterTitleStrokeColor(Color centerTitleStrokeColor) {
        mCenterTitleStrokePaint.setColor(centerTitleStrokeColor);
    }

    /**
     * Draw EquilateralTriangle
     *
     * @param p1 Start point
     * @param width The width of triangle
     * @param height The height of triangle
     * @param direction The direction of triangle
     * @return Path
     */
    private Path getEquilateralTriangle(Point p1, int width, int height,
                                        int direction) {
        Point p2 = null;
        Point p3 = null;

        // NORTH
        if (direction == TriangleDirection.NORTH.ordinal()) {
            p2 = new Point(p1.getPointX() + width, p1.getPointY());
            p3 = new Point(p1.getPointX() + (width >> 1),
                    (int) (height - Math.sqrt(FLOAT_THREE) / NUMBER_TWO * height));
        }

        // SOUTH
        else if (direction == TriangleDirection.SOUTH.ordinal()) {
            p2 = new Point(p1.getPointX(), p1.getPointY() - height);
            p3 = new Point(p1.getPointX() + width, p1.getPointY() - height);
            p1.modify(p1.getPointX() + (width >> 1),
                    (int) (Math.sqrt(FLOAT_THREE) / NUMBER_TWO * height));
        }

        // EAST
        else if (direction == TriangleDirection.EAST.ordinal()) {
            p2 = new Point(p1.getPointX(), p1.getPointY() - height);
            p3 = new Point((int) (Math.sqrt(FLOAT_THREE) / NUMBER_TWO * width),
                    p1.getPointY() / NUMBER_TWO);
        }

        // WEST
        else if (direction == TriangleDirection.WEST.ordinal()) {
            p2 = new Point(p1.getPointX() + width, p1.getPointY() - height);
            p3 = new Point(p1.getPointX() + width, p1.getPointY());
            p1.modify((int) (width - Math.sqrt(FLOAT_THREE) / NUMBER_TWO * width),
                    p1.getPointY() / NUMBER_TWO);
        }

        Path path = new Path();
        path.moveTo(p1.getPointX(), p1.getPointY());
        if (p2 != null) {
            path.lineTo(p2.getPointX(), p2.getPointY());
        }
        if (p3 != null) {
            path.lineTo(p3.getPointX(), p3.getPointY());
        }
        return path;
    }
}